package org.budo.support.spring.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.budo.support.lang.util.BooleanUtil;
import org.budo.support.lang.util.ListUtil;
import org.budo.support.lang.util.NumberUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.spring.bean.factory.util.BeanDefinitionUtil;
import org.budo.support.spring.bean.util.PropertyValueUtil;
import org.budo.support.spring.context.aware.BudoApplicationContextAware;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanIsAbstractException;
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer;
import org.springframework.beans.factory.config.PropertyResourceConfigurer;
import org.springframework.beans.factory.config.RuntimeBeanNameReference;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.PropertiesLoaderSupport;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.FrameworkServlet;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.config.spring.ReferenceBean;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 */
@Slf4j
public class SpringUtil {
    private static final String POINTCUT_ADVISOR = "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor";

    private static final Map<ServletContext, ApplicationContext> APPLICATION_IN_SERVLET_CACHE = new ConcurrentHashMap<ServletContext, ApplicationContext>();

    private static final Map<String, Object> BEAN_LIST_BY_TYPE_CACHE = new ConcurrentHashMap<String, Object>();

    private static final Map<String, Object> BEAN_INSTANCE_CACHE = new ConcurrentHashMap<String, Object>();

    /**
     * 如果一个方法本身，或者其继承覆盖的方法，被添加了指定注解，则返回这个(被覆盖的)方法，接口比超类优先
     */
    public static Method annotationPresentMethod(Method method, Class<? extends Annotation> annotationType) {
        if (null == method || null == annotationType) {
            return null;
        }

        if (method.isAnnotationPresent(annotationType)) {
            return method;
        }

        Class<?> declaringType = method.getDeclaringClass();

        Class<?>[] interfaceTypes = declaringType.getInterfaces(); // 似乎返回的是直接接口，间接的未返回
        if (null != interfaceTypes) {
            for (Class<?> interfaceType : interfaceTypes) {
                Method interfaceMethod = ReflectUtil.findMethod(interfaceType, method.getName(), method.getParameterTypes());
                if (null != interfaceMethod) {
                    Method annotationPresent = annotationPresentMethod(interfaceMethod, annotationType);
                    if (null != annotationPresent) {
                        return annotationPresent;
                    }
                }
            }
        }

        Class<?> superType = declaringType.getSuperclass();
        if (null != superType && !Object.class.equals(superType)) {
            Method superMethod = ReflectUtil.findMethod(superType, method.getName(), method.getParameterTypes());
            if (null != superMethod) {
                Method annotationPresent = annotationPresentMethod(superMethod, annotationType);
                if (null != annotationPresent) {
                    return annotationPresent;
                }
            }
        }

        return null;
    }

    public static <T> T getBeanByTypeAndActualTypes(ApplicationContext applicationContext, Class<T> beanType, Type... actualTypes) {
        Map<String, T> map = applicationContext.getBeansOfType(beanType);
        Set<Entry<String, T>> set = map.entrySet();
        for (Entry<String, T> entry : set) {
            T bean = entry.getValue();
            Class<?> type = ReflectUtil.beanType(bean.getClass());

            if (Arrays.equals(ReflectUtil.getSuperActualTypeArguments(type), actualTypes)) {
                return bean;
            }
        }

        return null;
    }

    /**
     * 注意，此方法未做缓存
     */
    public static <T> T getBean(Class<T> type) {
        BudoApplicationContextAware applicationContextAware = BudoApplicationContextAware.getInstanceNoNull();
        ApplicationContext applicationContext = applicationContextAware.getApplicationContext();
        return getBean(applicationContext, type);
    }

    public static Object getBean(String beanName) {
        BudoApplicationContextAware applicationContextAware = BudoApplicationContextAware.getInstanceNoNull();
        ApplicationContext applicationContext = applicationContextAware.getApplicationContext();
        return getBean(applicationContext, beanName);
    }

    /**
     * NoSuchBean 时返回null
     */
    public static <T> T getBean(ApplicationContext applicationContext, Class<T> type) {
        if (null == applicationContext || null == type) {
            log.error("#150 applicationContext=" + applicationContext + ", type=" + type);
            return null;
        }

        String[] beanNames = applicationContext.getBeanNamesForType(type);
        if (null == beanNames || beanNames.length < 1) {
            log.debug("#160 applicationContext={}, type={}, beanNames={}", applicationContext, type, Arrays.toString(beanNames));
            return null;
        }

        try {
            return applicationContext.getBean(type);
        } catch (NoSuchBeanDefinitionException e) {
            log.debug("#123 getBean error, type={}, applicationContext={}, e={}", type, applicationContext, e);
            return null;
        } catch (BeanNotOfRequiredTypeException e) {
            log.debug("#126 getBean error, type={}, applicationContext={}, e={}", type, applicationContext, e);
            return null;
        } catch (Throwable e) {
            if (("" + e).contains("NoClassDefFoundError-->org/apache/dubbo/common/bytecode/proxy1")) {
                log.error("#178 getBean error, applicationContext=" + applicationContext + ", type=" + type + ", e=" + e);
                return null;
            }

            log.error("#182 getBean error, applicationContext=" + applicationContext + ", type=" + type + ", e=" + e, e);
            return null;
        }
    }

    public static Object getBean(ApplicationContext applicationContext, String beanName, Boolean ignoreCreation) {
        return getBean(applicationContext, beanName, ignoreCreation, false);
    }

    /**
     * @param ignoreCreation 配置为true时 遇到 BeanCreationException 不打异常
     */
    public static Object getBean(ApplicationContext applicationContext, String beanName, Boolean ignoreCreation, Boolean initDubboReference) {
        if (null == applicationContext || null == beanName) {
            if (log.isDebugEnabled()) {
                log.debug("#186 getBean return null, applicationContext = " + applicationContext + ", beanName = " + beanName, new Throwable("getBean null"));
            } else {
                log.warn("#188 getBean return null, applicationContext = " + applicationContext + ", beanName = " + beanName);
            }

            return null;
        }

        try {
            ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
            DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();

            String beanDefinitionName = beanName;
            if (beanDefinitionName.startsWith("&")) { // 这部分似乎不应该放在这个方法里面
                beanDefinitionName = beanDefinitionName.substring(1);
            }

            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
            if (null == beanDefinition) {
                log.warn("#131 beanDefinition is null, beanName=" + beanName + ", beanDefinitionName=" + beanDefinitionName);
                return null;
            }

            if (beanDefinition.isAbstract()) {
                log.warn("#131 beanDefinition isAbstract, beanName=" + beanName + ", beanDefinitionName=" + beanDefinitionName);
                return null;
            }

            // 是未初始化的Dubbo引用
            String beanClassName = beanDefinition.getBeanClassName();
            if ("com.alibaba.dubbo.config.spring.ReferenceBean".equals(beanClassName)) {
                ReferenceBean<?> referenceFactory = (ReferenceBean<?>) applicationContext.getBean("&" + beanName);
                Object initialized = ReflectUtil.getFieldValue(ReferenceConfig.class, "initialized", referenceFactory);
                if ("false".equals(initialized + "") && BooleanUtil.isFalse(initDubboReference)) { // 未初始化
                    Object interfaceType = BeanDefinitionUtil.getPropertyValue(beanDefinition, "interface");
                    log.warn("#186 return null, ReferenceBean no init, interface=" + interfaceType + ", initDubboReference=" + initDubboReference);
                    return null;
                }
            }

            return applicationContext.getBean(beanName);
        } catch (BeanIsAbstractException e) {
            log.error("#98 getBean abstract, beanName=" + beanName + ", applicationContext=" + applicationContext + ", e=" + e);
            return null;
        } catch (NoSuchBeanDefinitionException e) {
            log.warn("#101 getBean NoSuchBeanDefinition, applicationContext=" + applicationContext + ", beanName=" + beanName + ", e=" + e);
            return null;
        } catch (BeanCreationException e) {
            if (null != ignoreCreation && ignoreCreation) {
                log.error("#120 getBean error, applicationContext=" + applicationContext + ", beanName=" + beanName + ", e=" + e);
            } else {
                log.error("#122 getBean error, applicationContext=" + applicationContext + ", beanName=" + beanName + ", e=" + e, e);
            }

            return null;
        } catch (IllegalStateException e) {
            if ((null != ignoreCreation && ignoreCreation) //
                    && (("" + e).contains("has not been refreshed yet") //
                            || ("" + e).contains("has been closed already"))) {
                log.error("#128 getBean error, applicationContext=" + applicationContext + ", beanName=" + beanName + ", e=" + e);
                return null;
            }

            log.error("#132 getBean error, applicationContext=" + applicationContext + ", beanName=" + beanName + ", e=" + e, e);
            return null;
        } catch (Throwable e) {
            if (("" + e).contains("No Scope registered for scope name")) {
                log.error("#126 getBean error, applicationContext=" + applicationContext + ", beanName=" + beanName + ", e=" + e);
                return null;
            }

            log.error("#104 getBean error, applicationContext=" + applicationContext + ", beanName=" + beanName + ", e=" + e, e);
            return null;
        }
    }

    public static Object getBean(ApplicationContext applicationContext, String beanName) {
        return getBean(applicationContext, beanName, false);
    }

    public static <T> List<T> getBeanListByType(ApplicationContext applicationContext, Class<T> type) {
        if (null == applicationContext || null == type) {
            log.error("#236 getBeanListByType return null, applicationContext=" + applicationContext + ", type=" + type);
            return null;
        }

        Map<String, T> beansOfType = applicationContext.getBeansOfType(type);
        Collection<T> beans = beansOfType.values();
        return new ArrayList<T>(beans);
    }

    public static ApplicationContext getApplicationContext(HttpServletRequest request) {
        if (null == request) {
            log.error("#162 request is null");
            return null;
        }

        HttpSession session = request.getSession();
        ServletContext servletContext = session.getServletContext();
        ApplicationContext applicationContext = APPLICATION_IN_SERVLET_CACHE.get(servletContext);
        if (null != applicationContext) {
            return applicationContext;
        }

        applicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        if (null != applicationContext) {
            APPLICATION_IN_SERVLET_CACHE.put(servletContext, applicationContext);
            return applicationContext;
        }

        Enumeration<String> enumeration = servletContext.getAttributeNames();
        while (enumeration.hasMoreElements()) {
            String name = enumeration.nextElement();
            if (StringUtil.startsWith(name, FrameworkServlet.SERVLET_CONTEXT_PREFIX)) {
                applicationContext = (ApplicationContext) servletContext.getAttribute(name);

                APPLICATION_IN_SERVLET_CACHE.put(servletContext, applicationContext);
                return applicationContext;
            }
        }

        log.error("#198 applicationContext not found, servletContext=" + servletContext + ", request=" + request);
        return null;
    }

    public static String getNameOfBean(ApplicationContext applicationContext, Object beanInstance) {
        String[] beanNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            if (applicationContext.getBean(beanName).equals(beanInstance)) {
                return beanName;
            }
        }

        log.info("#79 applicationContext=" + applicationContext + "beanInstance=" + beanInstance);
        return null;
    }

    public static BeanDefinition getBeanDefinition(ConfigurableListableBeanFactory beanFactory, String beanName) {
        if (!beanFactory.containsBeanDefinition(beanName)) {
            return null;
        }
        return beanFactory.getBeanDefinition(beanName);
    }

    public static BeanDefinition getBeanDefinition(ApplicationContext applicationContext, String beanName) {
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();

        if (!beanFactory.containsBeanDefinition(beanName)) {
            return null;
        }

        return beanFactory.getBeanDefinition(beanName);
    }

    public static BeanDefinition getBeanDefinition(BeanDefinitionRegistry beanDefinitionRegistry, String beanName) {
        if (!beanDefinitionRegistry.containsBeanDefinition(beanName)) {
            return null;
        }

        return beanDefinitionRegistry.getBeanDefinition(beanName);
    }

    public static BeanDefinition getBeanDefinition(ApplicationContext applicationContext, Class<?> interfaceType) {
        String[] beanNames = applicationContext.getBeanNamesForType(interfaceType);
        if (null == beanNames || beanNames.length < 1 || beanNames.length > 1) {
            throw new RuntimeException("#246 interfaceType=" + interfaceType + ", beanNames=" + Arrays.toString(beanNames) + ", applicationContext=" + applicationContext);
        }

        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
        return beanFactory.getBeanDefinition(beanNames[0]);
    }

    public static void removeBeanDefinition(String beanName) {
        ApplicationContext applicationContext = BudoApplicationContextAware.getInstanceNoNull().getApplicationContext();
        removeBeanDefinition(applicationContext, beanName);
    }

    public static void removeBeanDefinition(ApplicationContext applicationContext, String beanName) {
        if (null == applicationContext || null == beanName) {
            throw new IllegalArgumentException("#256 applicationContext=" + applicationContext + ", beanName=" + beanName);
        }

        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();

        beanFactory.removeBeanDefinition(beanName);
    }

    public static String getNameByBeanType(String beanType) {
        Class type = ReflectUtil.classForName(beanType);
        return getNameByBeanType(type);
    }

    public static String getNameByBeanType(ListableBeanFactory beanFactory, String beanType) {
        Class type = ReflectUtil.classForName(beanType);
        return getNameByBeanType(beanFactory, type);
    }

    public static String getNameByBeanType(Class<?> beanType) {
        ApplicationContext applicationContext = BudoApplicationContextAware.getInstanceNoNull().getApplicationContext();

        return getNameByBeanType(applicationContext, beanType);
    }

    public static String getNameByBeanType(ListableBeanFactory beanFactory, Class<?> beanType) {
        String[] beanNames = beanFactory.getBeanNamesForType(beanType);

        if (null == beanNames || beanNames.length < 1 || beanNames.length > 1) {
            throw new RuntimeException("#282 beanType=" + beanType + ", beanNames=" + Arrays.toString(beanNames));
        }

        return beanNames[0];
    }

    public static List<BeanDefinition> getBeanDefinitionListByInterface(BeanDefinitionRegistry beanDefinitionRegistry, Class<?> interfaceType) {
        if (null == beanDefinitionRegistry || null == interfaceType) {
            log.error("#375 getBeanDefinitionListByInterface return null, beanDefinitionRegistry=" + beanDefinitionRegistry + ", interfaceType=" + interfaceType);
            return null;
        }

        ConfigurableListableBeanFactory beanFactory = null;
        if (beanDefinitionRegistry instanceof ConfigurableListableBeanFactory) {
            beanFactory = (ConfigurableListableBeanFactory) beanDefinitionRegistry;
            return getBeanDefinitionListByInterface_0(beanFactory, interfaceType);
        }

        if (beanDefinitionRegistry instanceof GenericWebApplicationContext) {
            GenericWebApplicationContext genericWebApplicationContext = (GenericWebApplicationContext) beanDefinitionRegistry;
            ConfigurableListableBeanFactory listableBeanFactory = genericWebApplicationContext.getBeanFactory();
            return getBeanDefinitionListByInterface_0(listableBeanFactory, interfaceType);
        }

        ApplicationContext applicationContextParent = SpringUtil.getApplicationContextParent(beanDefinitionRegistry);
        if (null != applicationContextParent) {
            List<BeanDefinition> beanDefinitionListByInterface = getBeanDefinitionListByInterface((BeanDefinitionRegistry) applicationContextParent, interfaceType); // 递归
            return beanDefinitionListByInterface;
        }

        log.error("#370 getBeanDefinitionListByInterface return null, applicationContext=" + beanDefinitionRegistry + ", applicationContextParent=" + applicationContextParent);
        return null;

    }

    private static List<BeanDefinition> getBeanDefinitionListByInterface_0(ConfigurableListableBeanFactory beanFactory, Class<?> interfaceType) {
        List<BeanDefinition> list = new ArrayList<BeanDefinition>();

        String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
            String beanClassName = beanDefinition.getBeanClassName();
            if (StringUtil.isNullOrEmpty(beanClassName)) {
                log.debug("#408 getBeanDefinitionListByInterface continue, beanClassName={}, beanDefinitionName={}, interfaceType={}", //
                        beanClassName, beanDefinitionName, interfaceType);
                continue;
            }

            Class<?> beanType = ReflectUtil.classForNameOrNull(beanClassName);
            if (interfaceType.isAssignableFrom(beanType)) {
                list.add(beanDefinition);
            }
        }

        return list;
    }

    private static ApplicationContext getApplicationContextParent(BeanDefinitionRegistry beanDefinitionRegistry) {
        if (null == beanDefinitionRegistry) {
            log.error("#442 getApplicationContextParent, beanDefinitionRegistry=" + beanDefinitionRegistry);
            return null;
        }

        if (!(beanDefinitionRegistry instanceof ApplicationContext)) {
            if (log.isDebugEnabled()) {
                log.debug("#445 getApplicationContextParent, beanDefinitionRegistry=" + ObjectUtils.identityToString(beanDefinitionRegistry) //
                        + ", beanDefinitionRegistry=" + beanDefinitionRegistry, new Throwable("#446 getApplicationContextParent"));
            }

            log.warn("#447 getApplicationContextParent return null, beanDefinitionRegistry=" + ObjectUtils.identityToString(beanDefinitionRegistry));
            return null;
        }

        return ((ApplicationContext) beanDefinitionRegistry).getParent();
    }

    public static List<BeanDefinition> getBeanDefinitionListByInterface(Class<?> interfaceType) {
        ApplicationContext applicationContext = getApplicationContext();

        if (applicationContext instanceof BeanDefinitionRegistry) {
            BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) applicationContext;
            return getBeanDefinitionListByInterface(beanDefinitionRegistry, interfaceType);
        }

        if (null != applicationContext.getParent() && applicationContext.getParent() instanceof BeanDefinitionRegistry) {
            BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) applicationContext.getParent();
            return getBeanDefinitionListByInterface(beanDefinitionRegistry, interfaceType);
        }

        @SuppressWarnings("resource")
        AbstractApplicationContext abstractApplicationContext = (AbstractApplicationContext) applicationContext;
        ConfigurableListableBeanFactory beanFactory = abstractApplicationContext.getBeanFactory();
        if (null != beanFactory && beanFactory instanceof BeanDefinitionRegistry) {
            BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) beanFactory;
            return getBeanDefinitionListByInterface(beanDefinitionRegistry, interfaceType);
        }

        log.error("#442 getBeanDefinitionListByInterface return null, applicationContext=" + applicationContext);
        throw new RuntimeException("#443 applicationContext=" + applicationContext);
    }

    public static List<BeanDefinition> getBeanDefinitionListByType(ApplicationContext applicationContext, String beanType) {
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();

        BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) beanFactory;
        return getBeanDefinitionListByType(beanDefinitionRegistry, beanType);
    }

    public static List<BeanDefinition> getBeanDefinitionListByType(BeanDefinitionRegistry beanDefinitionRegistry, String beanType) {
        List<BeanDefinition> list = new ArrayList<BeanDefinition>();

        String[] beanDefinitionNames = beanDefinitionRegistry.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = beanDefinitionRegistry.getBeanDefinition(beanDefinitionName);
            String beanClassName = beanDefinition.getBeanClassName();
            if (StringUtil.equals(beanClassName, beanType)) {
                list.add(beanDefinition);
            }
        }

        return list;
    }

    public static BeanDefinition getAdvisorBeanDefinitionByAdviceType(BeanDefinitionRegistry beanDefinitionRegistry, String beanType) {
        List<BeanDefinition> advisorDefinitions = getBeanDefinitionListByType(beanDefinitionRegistry, POINTCUT_ADVISOR); // 所有Aop切面配置
        for (BeanDefinition advisorDefinition : advisorDefinitions) {
            MutablePropertyValues mutablePropertyValues = advisorDefinition.getPropertyValues();
            Object adviceBeanName = PropertyValueUtil.get(mutablePropertyValues, "adviceBeanName");
            if (!(adviceBeanName instanceof RuntimeBeanNameReference)) {
                continue;
            }

            String beanName = ((RuntimeBeanNameReference) adviceBeanName).getBeanName();
            BeanDefinition adviceDefinition = getBeanDefinition(beanDefinitionRegistry, beanName);
            if (null == adviceDefinition) {
                continue;
            }

            String beanClassName = adviceDefinition.getBeanClassName();
            if (StringUtil.equals(beanClassName, beanType)) {
                return advisorDefinition;
            }
        }

        log.info("#354 getAdvisorBeanDefinitionByAdviceType return null, beanType=" + beanType + ", beanDefinitionRegistry=" + ObjectUtils.identityToString(beanDefinitionRegistry));
        return null;
    }

    /**
     * @see org.springframework.context.support.AbstractApplicationContext#toString()
     */
    public static <T> T getBeanCached(ApplicationContext applicationContext, String beanName) {
        if (null == applicationContext || null == beanName) {
            log.error("#383 applicationContext=" + applicationContext + ", beanName=" + beanName);
            return null;
        }

        String cacheKey = applicationContext.getDisplayName() + "-" + applicationContext.getStartupDate() + "-" + applicationContext.hashCode() //
                + "-" + beanName;

        Object beanInstance = BEAN_INSTANCE_CACHE.get(cacheKey);
        if (null != beanInstance) {
            return (T) beanInstance;
        }

        beanInstance = getBean(applicationContext, beanName);
        if (null != beanInstance) {
            BEAN_INSTANCE_CACHE.put(cacheKey, beanInstance);
        }
        return (T) beanInstance;
    }

    public static String getPropertyValue(String propertyName) {
        // PreferencesPlaceholderConfigurer
        // PropertySourcesPlaceholderConfigurer
        // PropertyPlaceholderConfigurer
        if (null == propertyName || propertyName.trim().isEmpty()) {
            log.warn("#572 getPropertyValue, return null, propertyName=" + propertyName);
            return null;
        }

        String value = getPropertyValue_PropertySourcesPlaceholderConfigurer(propertyName);
        if (null != value && !value.trim().isEmpty()) {
            log.debug("#570 getPropertyValue, propertyName=" + propertyName + ", value=" + value);
            return value;
        }

        value = getPropertyValue_PreferencesPlaceholderConfigurer(propertyName);
        if (null != value && !value.trim().isEmpty()) {
            log.debug("#576 getPropertyValue, propertyName=" + propertyName + ", value=" + value);
            return value;
        }

        Environment environment = SpringUtil.getBean(Environment.class);
        value = environment.getProperty(propertyName);

        log.debug("#583 getPropertyValue, propertyName=" + propertyName + ", value=" + value);
        return value;
    }

    private static String getPropertyValue_PreferencesPlaceholderConfigurer(String propertyName) {
        List<String> beanNames = SpringUtil.getBeanNamesForType(PreferencesPlaceholderConfigurer.class);
        if (null == beanNames || beanNames.isEmpty()) {
            log.warn("#552 no PreferencesPlaceholderConfigurer, beanNames=" + beanNames + ", propertyName=" + propertyName);
            return null;
        }

        for (String beanName : beanNames) {
            PreferencesPlaceholderConfigurer placeholderConfigurer = (PreferencesPlaceholderConfigurer) getBean(beanName);
            if (null == placeholderConfigurer) {
                log.error("#616 placeholderConfigurer=" + placeholderConfigurer + ", beanName=" + beanName);
                return null;
            }

            Properties mergedProps = (Properties) ReflectUtil.invokeMethod(PropertiesLoaderSupport.class, "mergeProperties", new Class[0], placeholderConfigurer, new Object[0]);
            ReflectUtil.invokeMethod(PropertyResourceConfigurer.class, "convertProperties", new Class[] { Properties.class }, placeholderConfigurer, new Object[] { mergedProps });

            if (null == mergedProps || !mergedProps.containsKey(propertyName)) {
                log.error("#565 mergedProps = " + mergedProps + ", placeholderConfigurer=" + placeholderConfigurer + ", beanName=" + beanName + ", propertyName=" + propertyName);
                continue;
            }

            return mergedProps.getProperty(propertyName);
        }

        log.error("#569 return null, propertyName=" + propertyName + ", PreferencesPlaceholderConfigurer beanNames=" + beanNames);
        return null;
    }

    private static String getPropertyValue_PropertySourcesPlaceholderConfigurer(String propertyName) {
        List<String> beanNames = SpringUtil.getBeanNamesForType(PropertySourcesPlaceholderConfigurer.class);
        if (null == beanNames || beanNames.isEmpty()) {
            log.error("#620 getPropertyValue_PropertySourcesPlaceholderConfigurer return null, no PropertySourcesPlaceholderConfigurer, beanNames=" + beanNames + ", propertyName=" + propertyName);
            return null;
        }

        for (String beanName : beanNames) {
            PropertySourcesPlaceholderConfigurer placeholderConfigurer = (PropertySourcesPlaceholderConfigurer) getBean(beanName);
            if (null == placeholderConfigurer) {
                log.error("#640 placeholderConfigurer=" + placeholderConfigurer + ", beanName=" + beanName);
                return null;
            }

            String val = getPropertyValue_PropertySourcesPlaceholderConfigurer(placeholderConfigurer, propertyName);
            if (StringUtil.isNotNullOrEmpty(val)) {
                return val;
            }
        }

        log.warn("#429 return null, propertyName=" + propertyName + ", PropertySourcesPlaceholderConfigurer beanNames=" + beanNames);
        return null;
    }

    private static String getPropertyValue_PropertySourcesPlaceholderConfigurer(PropertySourcesPlaceholderConfigurer placeholderConfigurer, String propertyName) {
        MutablePropertySources propertySources = (MutablePropertySources) ReflectUtil.getFieldValue(PropertySourcesPlaceholderConfigurer.class, "propertySources", placeholderConfigurer);
        Iterator<PropertySource<?>> iterator = propertySources.iterator();

        while (iterator.hasNext()) {
            PropertySource<?> propertySource = (PropertySource<?>) iterator.next();
            Object propertyValue = propertySource.getProperty(propertyName);
            if (null != propertyValue && !("" + propertyValue).isEmpty()) {
                return propertyValue.toString();
            }
        }

        return null;
    }

    public static String getPropertyValue_2(String propertyName, String configLocation) {
        if (null != BudoApplicationContextAware.getInstance()) {
            return getPropertyValue(propertyName);
        }

        synchronized (SpringUtil.class) {
            if (null != BudoApplicationContextAware.getInstance()) {
                return getPropertyValue(propertyName);
            }

            new ClassPathXmlApplicationContext(configLocation);
            return getPropertyValue(propertyName);
        }
    }

    public static List<String> getBeanNamesForType(ApplicationContext applicationContext, Class<?> type) {
        if (null == applicationContext || null == type) {
            log.error("#441 getBeanNamesForType, applicationContext=" + applicationContext + ", type=" + type);
            return Collections.EMPTY_LIST;
        }

        String[] beanNamesForType = applicationContext.getBeanNamesForType(type);
        if (null == beanNamesForType || beanNamesForType.length < 1) {
            return Collections.EMPTY_LIST;
        }

        return Arrays.asList(beanNamesForType);
    }

    /**
     * @param beanIdPrefix 前缀
     */
    public static List<String> getBeanNamesForType(ApplicationContext applicationContext, Class<DruidDataSource> type, String beanIdPrefix) {
        List<String> beanIds = new ArrayList<String>();

        List<String> beanNamesForType = getBeanNamesForType(applicationContext, type);

        for (String each : beanNamesForType) {
            if (each.startsWith(beanIdPrefix)) { // 不是这个数据源
                beanIds.add(each);
            }
        }

        return beanIds;
    }

    public static String[] getBeanDefinitionNames() {
        ApplicationContext applicationContext = getApplicationContext();
        if (null == applicationContext) {
            log.error("#477 getBeanDefinitionNames return null, applicationContext=" + applicationContext);
            return null;
        }

        return applicationContext.getBeanDefinitionNames();
    }

    public static List<String> getBeanNamesForType(Class<?> type) {
        ApplicationContext applicationContext = getApplicationContext();
        if (null == applicationContext) {
            log.error("#710 getBeanNamesForType return null, type=" + type + ", applicationContext=" + applicationContext);
            return null;
        }

        return getBeanNamesForType(applicationContext, type);
    }

    public static ApplicationContext getApplicationContext() {
        BudoApplicationContextAware applicationContextAware = BudoApplicationContextAware.getInstance();
        if (null == applicationContextAware) {
            log.error("#720 getApplicationContext return null, applicationContextAware=" + applicationContextAware);
            return null;
        }

        ApplicationContext applicationContext = applicationContextAware.getApplicationContext();
        if (null == applicationContext) {
            log.error("#726 getApplicationContext return null, applicationContext=" + applicationContext + ", applicationContextAware=" + applicationContextAware);
            return null;
        }

        return applicationContext;
    }

    public static <T> T getBeanCached(Class<T> type) {
        ApplicationContext applicationContext = getApplicationContext();
        return getBeanCached(applicationContext, type);
    }

    /**
     * @see org.springframework.context.support.AbstractApplicationContext#toString()
     */
    public static <T> T getBeanCached(ApplicationContext applicationContext, Class<T> type) {
        if (null == applicationContext || null == type) {
            log.error("#383 applicationContext=" + applicationContext + ", type=" + type);
            return null;
        }

        String cacheKey = applicationContext.getDisplayName() //
                + "-" + applicationContext.getStartupDate() //
                + "-" + applicationContext.hashCode() //
                + "-type-" + type;

        Object beanInstance = BEAN_INSTANCE_CACHE.get(cacheKey);
        if (null != beanInstance) {
            return (T) beanInstance;
        }

        beanInstance = getBean(applicationContext, type);
        if (null != beanInstance) {
            BEAN_INSTANCE_CACHE.put(cacheKey, beanInstance);
        }

        return (T) beanInstance;
    }

    public static Object getBeanCached(String beanName) {
        ApplicationContext applicationContext = getApplicationContext();
        return getBeanCached(applicationContext, beanName);
    }

    public static Boolean containsBean(String beanId) {
        ApplicationContext applicationContext = getApplicationContext();
        return applicationContext.containsBean(beanId);
    }

    public static <T> List<T> getBeanListByType(Class<T> type) {
        ApplicationContext applicationContext = getApplicationContext();
        return getBeanListByType(applicationContext, type);
    }

    public static <T> List<T> getBeanListByTypeCached(Class<T> type) {
        if (null == type) {
            return null;
        }

        String cacheKey = type.getName();
        List<T> beanListByType = (List<T>) BEAN_LIST_BY_TYPE_CACHE.get(cacheKey);
        if (!ListUtil.isNullOrEmpty(beanListByType)) {
            return beanListByType;
        }

        beanListByType = getBeanListByType(type);
        if (!ListUtil.isNullOrEmpty(beanListByType)) {
            BEAN_LIST_BY_TYPE_CACHE.put(cacheKey, beanListByType);
        }

        return beanListByType;
    }

    public static <T> T getBean(String name, Class<T> requiredType) {
        BudoApplicationContextAware applicationContextAware = BudoApplicationContextAware.getInstanceNoNull();
        ApplicationContext applicationContext = applicationContextAware.getApplicationContext();
        return applicationContext.getBean(name, requiredType);
    }

    public static boolean isRootApplicationContextRefreshedEvent(ApplicationEvent event) {
        if (!isContextRefreshedEvent(event)) {
            return false;
        }

        ContextRefreshedEvent contextRefreshedEvent = (ContextRefreshedEvent) event;
        ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
        ApplicationContext parent = applicationContext.getParent();

        log.info("#780 event=" + event + ", applicationContext=" + applicationContext + ", parent=" + parent);
        if (null != parent) {
            return false;
        }

        return true;
    }

    public static Boolean isContextRefreshedEvent(ApplicationEvent event) {
        if (null == event) {
            log.error("#790 isContextRefreshedEvent, event=" + event);
            return null;
        }

        if (!(event instanceof ContextRefreshedEvent)) {
            return false;
        }

        ContextRefreshedEvent contextRefreshedEvent = (ContextRefreshedEvent) event;
        ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
        ApplicationContext parent = applicationContext.getParent();

        log.info("#902 event=" + event + ", applicationContext=" + applicationContext + ", parent=" + parent);
        return true;
    }

    public static Boolean isSpring40AtLeast() {
        return ReflectUtil.hasClass("org.springframework.core.io.PathResource");
    }

    public static Integer getPropertyValueInteger(String propertyName) {
        String propertyValue = getPropertyValue(propertyName);
        return NumberUtil.toInteger(propertyValue);
    }

    public static Long getPropertyValueLong(String propertyName) {
        String propertyValue = getPropertyValue(propertyName);
        return NumberUtil.toLong(propertyValue);
    }

    public static BigDecimal getPropertyValueBigDecimal(String propertyName) {
        String propertyValue = getPropertyValue(propertyName);
        return NumberUtil.toBigDecimal(propertyValue);
    }

    public static Boolean getPropertyValueBoolean(String propertyName) {
        String propertyValue = getPropertyValue(propertyName);
        return BooleanUtil.toBoolean(propertyValue);
    }

    public static String resolvePlaceholder(String input) {
        if (null == input || input.isEmpty()) {
            log.warn("#855 resolvePlaceholder, input=" + input);
            return input;
        }

        String output = new String(input);
        while (true) {
            int a = output.indexOf("${");
            if (a < 0) {
                return output;
            }

            int b = output.indexOf("}", a);
            if (b < 0) {
                throw new IllegalArgumentException("#920 output=" + output + ", input=" + input);
            }

            String key = input.substring(b, b);
            String val = getPropertyValue(key);
            if (null == val || val.isEmpty()) {
                throw new IllegalArgumentException("#926 output=" + output + ", input=" + input);
            }

            output = output.replace("${" + key + "}", val);
        }
    }

    public static Boolean isActive() {
        ApplicationContext applicationContext = getApplicationContext();
        return isActive(applicationContext);
    }

    public static Boolean isActive(ApplicationContext applicationContext) {
        if (null == applicationContext) {
            log.warn("#932 isActive return false, applicationContext=" + applicationContext);
            return false;
        }

        if (!(applicationContext instanceof AbstractApplicationContext)) {
            log.warn("#937 isActive return false, applicationContext=" + applicationContext);
            return false;
        }

        return ((AbstractApplicationContext) applicationContext).isActive();
    }

    public static String getPropertyValueNoNull(String propertyName) {
        String propertyValue = getPropertyValue(propertyName);

        if (null == propertyValue || propertyValue.trim().isEmpty()) {
            throw new IllegalArgumentException("#905 getPropertyValueNoNull, propertyValue=" + propertyValue + ", propertyName=" + propertyName);
        }

        return propertyValue;
    }

    public static Integer getPropertyValueInteger(String propertyName, Integer defaultValue) {
        Integer propertyValue = getPropertyValueInteger(propertyName);
        if (null != propertyValue) {
            return propertyValue;
        }

        return defaultValue;
    }

    public static List<String> getPropertyValueList(String propertyName, String regex) {
        String propertyValue = getPropertyValue(propertyName);
        if (null == propertyValue) {
            return null;
        }

        if (null == regex) {
            return Arrays.asList(propertyValue);
        }

        return Arrays.asList(propertyValue.split(regex));
    }

    public static List<String> getPropertyValueList(String propertyName) {
        return getPropertyValueList(propertyName, "\\,");
    }
}
